<?php
// 服务核心函数
use App\Exceptions\LoginException;
use App\Helpers\Redis\RedisHelper;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use Random\RandomException;

/**
 * 路由action 转换
 *
 * @tips 便于编辑器识别跳转
 *
 * @tips 举例 [PopularController::class, 'publish'] 等同于 PopularController@publish
 * @param array $action
 * @return false|string
 */
function makeRouteAction(array $action)
{
    $_action = implode('@', $action);
    return substr($_action, strrpos($_action, '\\') + 1);
}


/** 载入类
 *
 * @param string      $class
 * @param string|null $namespace
 *
 * @return mixed
 * @throws Exception
 */
function loadClass(string $class, string $namespace = null)
{
    // 类名
    $className = ucfirst($class);
    if (empty($namespace)) {
        $namespace = '';
    }
    $path = $namespace . '\\' . $className;
    // 用$path不用$className是为了避免$className名称冲突
    if (class_exists($path) === false) {
        throw new Exception('class ' . $className . ' does not exist', 500);
    }
    app()->bindIf($path, $path, true);

    return app($path);
}


/**
 * 加载service
 *
 * @param string $service
 * @return mixed
 * @throws Exception
 */
function loadService(string $service)
{
    return loadClass($service, 'App\\Services');
}

/**
 * 加载模型类
 *
 * @param string $model
 *
 * @return mixed
 */
function loadModel(string $model)
{
    return loadClass($model, 'App\\Models');
}

/**
 * 加载工具类
 *
 * @param string $helper
 *
 * @return mixed
 */
function loadHelper(string $helper)
{
    return loadClass($helper, 'App\\Helpers');
}

/**
 * 加载Library
 *
 * @param string $library
 *
 * @return mixed
 * @throws Exception
 */
function loadLibrary(string $library)
{
    return loadClass($library, 'App\\Library');
}

/**
 * 判断是否自定义异常
 *
 * @param Throwable $ex
 *
 * @return bool
 */
function isSubClassOfBaseException(Throwable $ex): bool
{
    return is_subclass_of($ex, 'App\\Exceptions\\BaseException');
}

/**
 * 检查是否是CLI(命令行)模式运行
 *
 * @return bool
 */
function checkCli(): bool
{
    return PHP_SAPI == 'cli';
}

/**
 * 获取请求时差
 *
 * @return mixed
 */
function getEquationTime()
{
    return env('APP_TIME_EQUATION', 10);
}

/**
 * 密码加密
 *
 * @param string $clearTextPassword 明文密码
 * @return string
 */
function encryption(string $clearTextPassword): string
{
    // 密码加密：此函数已携带随机盐值
    return password_hash($clearTextPassword, PASSWORD_BCRYPT);
}

/**
 * 验证明文密码
 *
 * @param string $clearTextPassword 明文密码
 * @param string $password          加密密码
 * @return void
 */
function encryptionCheck(string $clearTextPassword, string $password): bool
{
    return password_verify($clearTextPassword, $password);
}

/**
 * 验证码
 *
 * @return array
 */
function captcha(): array
{
    $width    = 120;
    $height   = 35;
    $size     = 6;
    $fontSize = 10;

    $image = imagecreatetruecolor($width, $height);

    $bgColor = imagecolorallocate($image, 255, 255, 255);

    imagefill($image, 0, 0, $bgColor);

    $content = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    $captcha = "";

    for ($i = 0; $i < $size; $i++) {
        $fontsize = $fontSize;

        $fontcolor = imagecolorallocate($image, mt_rand(0, 120), mt_rand(0, 120), mt_rand(0, 120));

        $fontContent = substr($content, mt_rand(0, strlen($content)), 1);

        $captcha .= $fontContent;

        $x = ($i * $width / $size) + mt_rand(5, 10);

        $y = mt_rand(5, 10);

        imagestring($image, $fontsize, $x, $y, $fontContent, $fontcolor);
    }

    imagepng($image);
    $content = ob_get_clean();
    imagedestroy($image);

    $base64 = 'data:image/png;base64,' . base64_encode($content);

    return [
        'captchaLength' => $size,
        'captchaPath'   => $base64,
        'captchaText'   => $captcha,
    ];
}

/**
 * 生成 token
 *
 * @tips 有效时间为7天
 * @param array $params
 * @return string
 */
function generateJwt(array $params): string
{
    $key = env('JWT_SECRET');

    $payload = [
        'iat'         => time(),
        'exp'         => time() + 7 * 24 * 3600,
        'userId'      => $params['userId'],
        'uuid'        => $params['uuid'],
        'nickName'    => $params['nickName'],
        'username'    => $params['username'],
        'authorityId' => $params['authorityId'],
    ];

    return JWT::encode($payload, $key, 'HS256');
}

/**
 * 验证 jwt Token
 *
 * @param $token string token
 * @return array
 * @throws Exception
 */
function verifyJwt(string $token): array
{
    // 判断token是否在黑名单中
    $redisKey = RedisHelper::makeKey(RedisHelper::STRING_JWT_BLACK_LIST, $token);
    if (RedisHelper::get($redisKey)) {
        throw new LoginException("Token已过期，请重新登录", LoginException::CODE_1006);
    }

    $key = env('JWT_SECRET');
    try {
        $result = JWT::decode($token, new Key($key, 'HS256'));
        return json_decode(json_encode($result), true);
    } catch (ExpiredException $e) {
        throw new LoginException("Token已过期，请重新登录", LoginException::CODE_1006);
    } catch (SignatureInvalidException $e) {
        throw new LoginException("Token签名无效，请重新登录", LoginException::CODE_1007);
    } catch (BeforeValidException $e) {
        throw new LoginException("Token尚未有效，请重新登录", LoginException::CODE_1008);
    } catch (Exception $e) {
        throw new LoginException("Token验证失败，请重新登录", LoginException::CODE_1009);
    }
}

/**
 * jwt 加入黑名单
 *
 * @param string $token    jwtToken
 * @param int    $expireAt 过期时间
 * @return void
 */
function jwtJoinBlackList(string $token, int $expireAt): void
{
    $redisKey = RedisHelper::makeKey(RedisHelper::STRING_JWT_BLACK_LIST, $token);
    RedisHelper::set($redisKey, 1, $expireAt + ONE_MINUTE); // 多储存1分钟时间
}


/**
 * 生成uuid
 *
 * @throws RandomException
 */
function generateUuid(): string
{
    $data    = random_bytes(16);
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40); // 设置版本号为4
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80); // 设置变体标志位
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
